<?php

namespace liketp\foundation;

use liketp\exception\Exception;
use liketp\Log;
use Throwable;

class EventLoop
{
    protected $read = [];
    protected $write = [];
    protected $except = [];
    protected $allEvents = [];

    protected $timer;
    protected $maxSleep = 10 * 1000;

    public function __construct()
    {
        $this->timer = app('timer');
        $this->timer->init();
    }

    public function add($fd, $flag, $func, $args = [])
    {
        switch ($flag) {

            case 'READ':
                $fd_key = (int)$fd;
                $this->read[] = $fd;
                $this->allEvents[$fd_key][$flag] = [$func, [$fd]];
                break;

            case 'timer':
            case 'timerInterval':
                $persistant = $flag == 'timerInterval' ? true : false;
                $this->timer->add($fd, $func, $args, $persistant);
                break;
        }
    }

    public function loop()
    {
        while (true) {
            $read = $this->read;
            $write = $this->write;
            $except = $this->except;

            $res = 0;
            $delay = $this->getInterval();

            if ($read || $write || $except) {
                $res = socket_select($read, $write, $except, 0, $delay);
            } else {
                usleep($delay);
            }

            if ($res === false) throw new Exception('EventLoop exception');

            $this->tick();

            if ($res == 0) continue;

            if ($read) {
                foreach ($read as $fd) {
                    $fd_key = (int)$fd;
                    $eventData = $this->allEvents[$fd_key]['READ'] ?? null;

                    if (is_array($eventData) == false || count($eventData) != 2) {
                        Log::record('EventLoop exception: can\'t find event data', 'error');
                        continue;
                    }

                    try {
                        call_user_func_array($eventData[0], $eventData[1]);
                    } catch (Throwable $e) {
                        Log::record('EventLoop exception: ' . $e->getMessage(), 'error');
                    }
                }
            }
        };
    }


    //------------------------------------------------ protected
    protected function tick()
    {
        if ($this->timer->recentTriggerTime(true) > 0) return;

        $this->timer->tick();
    }

    protected function getInterval()
    {
        if ($this->timer && $this->timer->isEmpty() == false) {
            $delay = $this->timer->recentTriggerTime(true);
            $delay = min($delay, $this->maxSleep);
            return $delay;
        } else {
            return $this->maxSleep;
        }
    }
}
